package utils.dataComb;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 结对匹配，尽量符合正交匹配原则的数据组合
 */
public class Pairwise extends Criteria {


    public final static String EMPTY = "_";


    public Pairwise(List<List<Object>> parametersInputValues) {
        super(parametersInputValues);
    }


    @Override
    public Set<List<Object>> getCombinations() {

        if (getParametersInputValues().isEmpty() || getParametersInputValues().size() < 2) {
            return generateOneParameterCombination();
        } else {
            List<List<Object>> initial = initialize(getParametersInputValues().get(0), getParametersInputValues().get(1));
            Set<List<Object>> combinations = new HashSet<>();

            if (getParametersInputValues().size() <= 2) {
                combinations.addAll(initial);
            } else {
                combinations.addAll(inParameterOrderGeneration(initial));
            }
            return combinations;
        }

    }

    public Object[][] getArrayCombinations() {
        Set<List<Object>> set = getCombinations();
        Object[][] objects = new Object[set.size()][];
        int num = 0;
        for (List<Object> list : set) {

            objects[num] = list.toArray();
            num++;
        }
        return objects;

    }


    private Set<List<Object>> generateOneParameterCombination() {
        Set<List<Object>> combinations = new HashSet<>();

        if (getParametersInputValues().size() == 1) {
            List<Object> singleElement = new ArrayList<>();
            singleElement.add(getParametersInputValues().get(0).get(0));
            combinations.add(singleElement);
        }
        return combinations;
    }


    private List<List<Object>> inParameterOrderGeneration(List<List<Object>> initialPairs) {
        List<List<Object>> combinations = null;
        for (int paramIndex = 2; paramIndex < getParametersInputValues().size(); paramIndex++) {
            PairCollection uncoveredPairs = horizontalGrowth(initialPairs, getParametersInputValues().get(paramIndex), getParametersInputValues().subList(0, paramIndex));
            combinations = new ArrayList<>(verticalGrowth(initialPairs, uncoveredPairs, paramIndex));
            initialPairs = combinations;
        }
        return combinations;
    }


    private Set<List<Object>> verticalGrowth(List<List<Object>> combinations, PairCollection uncoveredPairs, int paramIndex) {
        List<List<Object>> tempCombinations = new ArrayList<>();

        for (TestPair testPair : uncoveredPairs.getPairs()) {
            List<Object> comb = createCombinationForVerticalGrowth(paramIndex);
            comb.set(testPair.getValueAIndex(), testPair.getValueA());
            comb.set(testPair.getValueBIndex(), testPair.getValueB());
            tempCombinations.add(comb);
        }

        for (TestPair testPair : uncoveredPairs.getPairs()) {
            for (List<Object> combination : tempCombinations) {
                boolean growingParamEqualsCombination = combination.get(testPair.getValueAIndex()).equals(testPair.getValueA());
                boolean otherValueIsEmpty = combination.get(testPair.getValueBIndex()).equals(EMPTY);
                if (growingParamEqualsCombination && otherValueIsEmpty) {
                    combination.set(testPair.getValueBIndex(), testPair.getValueB());
                }
            }
        }

        for (List<Object> combination : tempCombinations) {
            if (combination.contains(EMPTY)) {
                replaceEmptyValues(combination);
            }
        }

        Set<List<Object>> newCombinations = new HashSet<>();
        newCombinations.addAll(combinations);
        newCombinations.addAll(tempCombinations);

        return newCombinations;
    }


    private void replaceEmptyValues(List<Object> combination) {
        for (int i = 0; i < combination.size(); i++) {
            if (combination.get(i).equals(EMPTY)) {
                combination.set(i, getParametersInputValues().get(i).get(0));
            }
        }
    }


    private List<Object> createCombinationForVerticalGrowth(int paramIndex) {
        List<Object> combination = new ArrayList<>(3);
        for (int i = 0; i <= paramIndex; i++) {
            combination.add(EMPTY);
        }
        return combination;
    }


    private PairCollection horizontalGrowth(List<List<Object>> initialPairs, List<Object> inputValuesForGrowth, List<List<Object>> previouslyGrownValues) {

        PairCollection uncoveredPairs = new PairCollection(inputValuesForGrowth, previouslyGrownValues, previouslyGrownValues.size());

        if (initialPairs.size() <= inputValuesForGrowth.size()) {
            return growCombinationsAndRemoveCoveredPairs(initialPairs, inputValuesForGrowth, uncoveredPairs);
        } else {
            PairCollection uncoveredCombinations = growCombinationsAndRemoveCoveredPairs(initialPairs, inputValuesForGrowth, uncoveredPairs);

            for (int i = inputValuesForGrowth.size(); i < initialPairs.size(); i++) {
                List<Object> newCombination = uncoveredPairs.getCombinationsThatCoversMostPairs(initialPairs.get(i), inputValuesForGrowth);
                initialPairs.set(i, newCombination);
                uncoveredPairs.removePairsCoveredBy(newCombination);
            }

            return uncoveredCombinations;
        }

    }


    private PairCollection growCombinationsAndRemoveCoveredPairs(List<List<Object>> combinations, List<Object> inputValuesForGrowth, PairCollection uncoveredPairs) {

        for (int i = 0; i < combinations.size(); i++) {
            if (i >= inputValuesForGrowth.size()) {
                break;
            } else {
                combinations.get(i).add(inputValuesForGrowth.get(i));
                uncoveredPairs.removePairsCoveredBy(combinations.get(i));
            }
        }

        return uncoveredPairs;

    }


    private List<List<Object>> initialize(List<Object> firstParamValues, List<Object> secondParamValues) {
        List<List<Object>> initialCombinations = new ArrayList<>();

        for (Object firstParamValue : firstParamValues) {
            for (Object secondParamValue : secondParamValues) {
                List<Object> combination = new ArrayList<>();
                combination.add(firstParamValue);
                combination.add(secondParamValue);
                initialCombinations.add(combination);
            }
        }

        return initialCombinations;
    }
}
